#include <stdio.h>
#include <stdlib.h>
#include <expat.h>
#include "parser.h"

#define BUFFER_SIZE 10240

typedef enum LabelType {
    XEL_REPORT,
    XEL_FILE_LIST,
    XEL_FILE,
    XEL_START_BOOT,
    XEL_FIELD,
    XEL_ACTION_LIST,
    XEL_ACTION,
    XEL_APIARG_LIST,
    XEL_APIARG,
    XEL_EXINFO_LIST,
    XEL_EXINFO,
    XEL_ERR
} XMLElemType;

typedef struct XMLElemInfo{
    XMLElemType type;
    GData *attr;
    GStringChunk *str_pool;
} XMLElemInfo;

typedef struct ActionState{
    gchar *api_name;
    gchar *call_name;
    gchar *call_pid;
    gchar *call_time;
    gchar *ret_value;
    GPtrArray *api_args;
    GPtrArray *exinfo;
    GStringChunk *str_pool;
} ActionState;

typedef struct SandboxParserCtx {
    ActionState act_state;
    size_t depth;
    XMLElemInfo *curr_elem;
    GQueue *elems;
} SandboxParserCtx;

typedef struct SandboxParser {
    SandboxParserCtx ctx;
    XML_Parser xml_parser;
    LogElem *le;
    size_t buf_len;
    int error;
    int incoming;
    char buf[BUFFER_SIZE];
} SandboxParser;

static void
action_state_clear (ActionState *state) {
    state->api_name = NULL;
    state->call_name = NULL;
    state->call_pid = NULL;
    state->call_time = NULL;
    state->ret_value = NULL;
    g_ptr_array_set_size (state->exinfo, 0);
    g_ptr_array_set_size (state->api_args, 0);
    g_string_chunk_clear (state->str_pool);
}

static int
action_state_init (ActionState *state) {
    state->api_name = NULL;
    state->call_name = NULL;
    state->call_pid = NULL;
    state->call_time = NULL;
    state->ret_value = NULL;
    state->api_args = g_ptr_array_new ();
    state->exinfo = g_ptr_array_new ();
    state->str_pool = g_string_chunk_new (128);
    return 0;
}

static int
action_state_destory (ActionState *state) {
    g_ptr_array_free (state->api_args,TRUE);
    g_ptr_array_free (state->exinfo,TRUE);
    g_string_chunk_free (state->str_pool);
    return 0;
}

static int
action_state_update (ActionState *state, XMLElemInfo *info) {
    gpointer tmp_ptr, data;
    switch (info->type)
    {
    case XEL_ACTION:
        tmp_ptr = g_datalist_get_data (&(info->attr), "api_name");
        state->api_name = g_string_chunk_insert_const (state->str_pool, 
                                                (const gchar*) tmp_ptr);
        tmp_ptr = g_datalist_get_data (&(info->attr), "call_name");
        state->call_name = g_string_chunk_insert_const (state->str_pool, 
                                                (const gchar*) tmp_ptr);
        tmp_ptr = g_datalist_get_data (&(info->attr), "call_pid");
        state->call_pid = g_string_chunk_insert_const (state->str_pool, 
                                                (const gchar*) tmp_ptr);
        tmp_ptr = g_datalist_get_data (&(info->attr), "call_time");
        state->call_time = g_string_chunk_insert_const (state->str_pool, 
                                                (const gchar*) tmp_ptr);
        tmp_ptr = g_datalist_get_data (&(info->attr), "ret_value");
        state->ret_value = g_string_chunk_insert_const (state->str_pool, 
                                                (const gchar*) tmp_ptr);
        break;
    case XEL_APIARG:
        tmp_ptr = g_datalist_get_data (&(info->attr), "value");
        data = g_string_chunk_insert_const (state->str_pool, 
                                                (const gchar*) tmp_ptr);
        g_ptr_array_add (state->api_args, data);
        break;
    case XEL_EXINFO:
        tmp_ptr = g_datalist_get_data (&(info->attr), "value");
        data = g_string_chunk_insert_const (state->str_pool, 
                                                (const gchar*) tmp_ptr);
        g_ptr_array_add (state->exinfo, data);
        break;
    default:
        break;
    }
    return 0;
}


static inline XMLElemType
match_xml_elem_type (const char *el) {
    if (!strcmp (el, "exInfo"))
        return XEL_EXINFO;
    if (!strcmp (el, "apiArg"))
        return XEL_APIARG;
    if (!strcmp (el, "exInfo_list"))
        return XEL_EXINFO_LIST;
    if (!strcmp (el, "apiArg_list"))
        return XEL_APIARG_LIST;
    if (!strcmp (el, "action_list"))
        return XEL_ACTION_LIST;
    if (!strcmp (el, "action"))
        return XEL_ACTION;
    if (!strcmp (el, "report"))
        return XEL_REPORT;
    if (!strcmp (el, "start_boot"))
        return XEL_START_BOOT;
    if (!strcmp (el, "field"))
        return XEL_FIELD;
    if (!strcmp (el, "file"))
        return XEL_FILE;
    if (!strcmp (el, "file_list"))
        return XEL_FILE_LIST;
    return XEL_ERR;
}

static void XMLCALL
elem_start (void *data, const char *el, const char **attr) {
    SandboxParser *sp = (SandboxParser *)data;
    XMLElemType type = match_xml_elem_type (el);
    XMLElemInfo *info = g_new0 (XMLElemInfo, 1);
    if (type == XEL_ERR) {
        sp->error = 1;
        goto out;
    }
    g_datalist_init (&info->attr);
    info->str_pool = g_string_chunk_new (128);
    size_t i;
    gchar *attr_val;
    for (i = 0; attr[i] != NULL; i+=2) {
        attr_val = g_string_chunk_insert (info->str_pool, attr[i+1]);
        g_datalist_set_data (&info->attr, attr[i], attr_val);
    }
    info->type = type;
    sp->ctx.curr_elem = info;
    g_queue_push_tail (sp->ctx.elems, info);
    /* 读入Action */
    if (type == XEL_ACTION) {
        action_state_clear (&(sp->ctx.act_state));
    }
    action_state_update (&(sp->ctx.act_state), info);
    
    out:
    sp->ctx.depth++;
    return;
}

static void
xml_elem_info_destory (XMLElemInfo *info) {
    g_string_chunk_free (info->str_pool);
    g_datalist_clear (&(info->attr));
    g_free (info);
}


static void XMLCALL
elem_end (void *data,  const char *el) {
    SandboxParser *sp = (SandboxParser *)data;
    XMLElemType type = match_xml_elem_type (el);
    if (type == XEL_ACTION) {
        int i;
        char * str = NULL;
        XML_StopParser (sp->xml_parser, TRUE);
        for (i = 0; i < sp->ctx.act_state.exinfo->len; i++) {
            str = g_ptr_array_index (sp->ctx.act_state.exinfo, i);
            if (strstr (str, "C:\\") != NULL) {
                break;
            }
        }
        sp->le->dest = NULL;
        sp->le->src = NULL;
        sp->le->dest_type = LEP_FILE;
        if (str != NULL)
            sp->le->dest = g_string_chunk_insert_const (sp->le->str_pool,str);
        sp->le->src_type = LEP_PID;
        if (str != NULL)
            sp->le->src = g_string_chunk_insert_const (sp->le->str_pool,sp->ctx.act_state.call_pid);
        sp->le->type = LE_REFER;
        str = g_string_chunk_insert (sp->le->str_pool, sp->ctx.act_state.call_name);
        g_ptr_array_add (sp->le->extra, str);
        sp->incoming = TRUE;
    }
    g_queue_pop_tail (sp->ctx.elems);
    xml_elem_info_destory (sp->ctx.curr_elem);
    sp->ctx.curr_elem = g_queue_peek_tail (sp->ctx.elems);
    fflush (stdout);
    sp->ctx.depth--;
}


static int
sandbox_parser_create (SandboxParser **p) {
    if (p == NULL) goto fail; 
    *p = g_new0 (SandboxParser, 1);
    if (*p == NULL) goto fail;

    if(((*p)->ctx.elems = g_queue_new ()) == NULL) {
        g_free (*p);
        goto fail;
    }
    if (((*p)->xml_parser = XML_ParserCreate (NULL)) == NULL) {
        g_free (*p);
        goto fail;
    }
    XML_SetUserData ((*p)->xml_parser, (*p));
    XML_SetStartElementHandler ((*p)->xml_parser, elem_start);
    XML_SetEndElementHandler ((*p)->xml_parser, elem_end);
    action_state_init (&((*p)->ctx.act_state));
    (*p)->ctx.depth = 0;
    (*p)->ctx.curr_elem = NULL;
    (*p)->buf_len = 0;
    return 0;
    fail:
    return -1;
}


static int
sandbox_parser_parse (void *opaque, FILE *f, LogElem *le) {
    SandboxParser *sp = (SandboxParser *) opaque;
    enum XML_Status stat;
    XML_ParsingStatus pstat;
    clean_log_elem (le);
    sp->le = le;
    sp->error = FALSE;
    sp->incoming = FALSE;
    size_t nread;
    for (;;) {
        XML_GetParsingStatus (sp->xml_parser, &pstat);
        if (pstat.parsing ==  XML_INITIALIZED || pstat.parsing == XML_PARSING) {
            nread = fread (sp->buf, 1, BUFFER_SIZE, f);
            sp->buf_len = nread;
            if (nread == 0) {
                stat = XML_Parse (sp->xml_parser, sp->buf, 0, TRUE);
                break;
            } else {
                stat = XML_Parse (sp->xml_parser, sp->buf, sp->buf_len, FALSE);
            }
        } else if (pstat.parsing == XML_SUSPENDED){
            stat = XML_ResumeParser (sp->xml_parser);
        }
        if (stat == XML_STATUS_ERROR) {
            sp->error = TRUE;
            break;
        }
        if (sp->incoming == TRUE) {
            break;
        }
    }
    if (sp->error)
        return PARSE_LOG_ERR;
    else if (sp->incoming)
        return PARSE_LOG_INCOMING;
    else
        return PARSE_LOG_END;
}

static int
sandbox_parser_destruct (void *opaque) {

}

int
sandbox_log_parser_create (LogParser **p, FILE *f) {
    SandboxParser *sp;
    int stat;
    if (p == NULL) goto error;
    *p = g_new0 (LogParser, 1);
    if (*p == NULL) goto error;
    
    stat = sandbox_parser_create (&sp);
    
    if (stat != 0) {
        g_free (*p);
        goto error;
    }

    (*p)->opaque = sp;
    (*p)->log_file = f;
    (*p)->parse = sandbox_parser_parse;
    (*p)->destruct = sandbox_parser_destruct;
    return 0;
    error:
    return -1;
}